#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using Castle.DynamicProxy;
using log4net;
using Karma.Framework.Core.IoC.Configuration;
using Karma.Framework.Core.Utils;

#endregion

namespace Karma.Framework.Core.IoC.Extensibility
{
    public class DefaultExtensionManager : IExtensionManager
    {
        private static readonly ProxyGenerator ProxyGen = new ProxyGenerator();

        private static IDictionary<string, Type[]> _interceptors;

        private static IDictionary<string, Type[]> _mixins;
        private static Type[] _proxiableTypes;
        private static ILog LOG = LogManager.GetLogger(typeof (DefaultExtensionManager).Name);

        #region IExtensionManager Members

        public T ProcessExtensions<T>(T instance)
        {
            if (IsSubjectToProxify<T>())
            {
                if (LOG.IsDebugEnabled)
                {
                    LOG.Debug("Processing extensions.");
                }
                var interceptors = GetInterceptors<T>();
                if (LOG.IsDebugEnabled)
                {
                    LOG.Debug(string.Format("Interceptors to apply: {0}.", interceptors.ItemsToString()));
                }
                var pgo = new ProxyGenerationOptions();
                var proxy = ProxyGen.CreateInterfaceProxyWithTarget<T>(instance, pgo, interceptors);
                if (LOG.IsDebugEnabled)
                {
                    LOG.Debug(string.Format("Generated proxy: {0}.", proxy));
                }
                return proxy;
                //return instance;
            }
            return instance;
        }

        #endregion

        private static bool IsSubjectToProxify<T>()
        {
            var thetype = typeof (T);
            if (_proxiableTypes == null)
            {
                _proxiableTypes = (from e in IoCConfiguration.Instance.Extensions
                                   let atts =
                                       e.GetCustomAttributes(typeof (PlugInApplicationConstraintsAttribute), true)
                                   from att in atts
                                   let types = ((PlugInApplicationConstraintsAttribute) att).Types.AsEnumerable()
                                   from t in types
                                   select t).ToArray();
            }
            foreach (var type in _proxiableTypes)
            {
                if (type.IsGenericType)
                {
                    if (thetype.GetInterfaces().Any(x => x.IsGenericType &&
                                                         x.GetGenericTypeDefinition().Equals(type)))
                    {
                        return true;
                    }
                }
                else
                {
                    if (type.IsAssignableFrom(thetype))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        private static Castle.Core.Interceptor.IInterceptor[] GetInterceptors<T>()
        {
            var thetype = typeof (T);
            if (_interceptors == null)
            {
                _interceptors = new Dictionary<string, Type[]>();
            }
            if (!_interceptors.ContainsKey(thetype.FullName))
            {
                _interceptors.Add(thetype.FullName, GetApplicableTypes(thetype,
                                                                       IoCConfiguration.Instance.Interceptors));
            }
            return (from t in _interceptors[thetype.FullName]
                    select (Castle.Core.Interceptor.IInterceptor) Activator.CreateInstance(t)).ToArray();
        }

        private static Castle.Core.Interceptor.IInterceptor[] GetMixins<T>()
        {
            var thetype = typeof (T);
            if (_mixins == null)
            {
                _mixins = new Dictionary<string, Type[]>();
            }
            if (!_mixins.ContainsKey(thetype.FullName))
            {
                _mixins.Add(thetype.FullName, GetApplicableTypes(thetype,
                                                                 IoCConfiguration.Instance.Mixins));
            }
            return (from t in _interceptors[thetype.FullName]
                    select (Castle.Core.Interceptor.IInterceptor) Activator.CreateInstance(t)).ToArray();
        }

        private static Type[] GetApplicableTypes(Type thetype, Type[] source)
        {
            var list = new List<Type>();
            foreach (var interceptor in source)
            {
                var attribute = (PlugInApplicationConstraintsAttribute)
                                interceptor.GetCustomAttributes(
                                    typeof (PlugInApplicationConstraintsAttribute), true).First();
                foreach (var type in attribute.Types)
                {
                    if (type.IsGenericType)
                    {
                        if (thetype.GetInterfaces().Any(x => x.IsGenericType &&
                                                             x.GetGenericTypeDefinition().Equals(type)))
                        {
                            list.Add(interceptor);
                            break;
                        }
                    }
                    else
                    {
                        if (type.IsAssignableFrom(thetype))
                        {
                            list.Add(interceptor);
                            break;
                        }
                    }
                }
            }
            return list.ToArray();
        }
    }
}